import asyncio
import json
import jsonpath
import base64
import io
from PIL import Image
import cv2
import numpy as np
import random
import time
from playwright.async_api import async_playwright

async def run():
    async with async_playwright() as playwright:
        # 启动浏览器
        browser = await playwright.chromium.launch(
            headless=False,  # 显示浏览器
            slow_mo=50,  # 操作减速
            devtools=False,  # 关闭开发者工具
            downloads_path="downloads",  # 下载路径
            args=[
                '--start-maximized',  # 最大化窗口
                '--disable-infobars',  # 禁用信息栏
                '--no-sandbox',  # 禁用沙箱
                '--disable-notifications'  # 禁用通知
            ],
            ignore_default_args=['--enable-automation']  # 忽略默认参数
        )
        # 创建新页面
        # 设置视窗大小
        context = await browser.new_context(
            viewport={'width': 1440, 'height': 900},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
        )
        # 创建页面
        page = await context.new_page()

        # 导航到网页
        await page.goto("https://kq.neusoft.com/login")

        # 等待页面加载
        await page.wait_for_load_state("networkidle")

        try:
            # 定位并填写用户名
            username_input = page.locator('input[type="text"]')
            await username_input.fill('meng.wl')

            # 定位并填写密码
            password_input = page.locator('input[type="password"]')
            await password_input.fill('Silence$000$')

            await slide_captcha_image(page)


        except Exception as e:
            print(f"登录失败: {e}")

        # 等待一段时间以便查看
        time.sleep(20)

        # 关闭浏览器
        await browser.close()


async def slide_captcha_image(page):
    # 等地啊响应，当发生点击事件时候，等待这个接口的返回值
    async with page.expect_response("**/imageRoute") as image_route_response_info:
        # 点击按钮
        login_button = page.locator('#loginButton')
        await login_button.click()

        # 等待响应
        response = await image_route_response_info.value
        response_text = await response.text()
        json_data = json.loads(response_text)

        # 获取base64图片数据
        base64_str = jsonpath.jsonpath(json_data, "$..originImage")[0]

        # 处理base64图片
        image_data = process_base64_image(base64_str)

        # 获取滑块元素
        slider = page.locator('.captcha-control-button')  # 请根据实际选择器调整

        # 分析图像旋转角度
        slide_distance = analyze_rotation_captcha(image_data)

        # 执行滑动操作
        await slide_captcha(page, slider, slide_distance)

        # 等待验证结果
        await page.wait_for_load_state("networkidle")

        while slider is not None:
            await slide_captcha_image(page)


def process_base64_image(base64_str):
    """
    将base64字符串转换为图像数据
    """
    # 移除可能的前缀
    if ',' in base64_str:
        base64_str = base64_str.split(',')[1]

    # 解码Base64数据
    image_bytes = base64.b64decode(base64_str)

    # 使用PIL打开图像
    image = Image.open(io.BytesIO(image_bytes))

    # 转换为OpenCV格式
    image_np = np.array(image)
    if image_np.shape[2] == 4:  # 如果是RGBA，转换为RGB
        image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)

    return image_np


def analyze_rotation_captcha(image, debug=False):
    """
    专门针对图片旋转验证码的分析函数
    """
    # 创建调试图像
    debug_img = image.copy() if debug else None
    height, width = image.shape[:2]

    # 1. 提取圆形区域
    mask = np.zeros(image.shape[:2], dtype=np.uint8)
    center = (width // 2, height // 2)
    radius = min(width, height) // 2 - 10
    cv2.circle(mask, center, radius, 255, -1)
    masked = cv2.bitwise_and(image, image, mask=mask)

    # 2. 检测场景特征
    # 转换到HSV颜色空间进行分析
    hsv = cv2.cvtColor(masked, cv2.COLOR_BGR2HSV)

    # 提取饱和度通道 - 通常天空饱和度低，地面饱和度高
    saturation = hsv[:, :, 1]

    # 分析图像四个象限的特征
    quarter_height, quarter_width = height // 4, width // 4

    # 计算每个区域的特征
    regions = []
    for i in range(4):
        for j in range(4):
            y_start = i * quarter_height
            y_end = (i + 1) * quarter_height
            x_start = j * quarter_width
            x_end = (j + 1) * quarter_width

            region = masked[y_start:y_end, x_start:x_end]
            if np.sum(region) == 0:  # 跳过空区域
                continue

            # 计算区域特征
            brightness = np.mean(cv2.cvtColor(region, cv2.COLOR_BGR2GRAY))
            sat = np.mean(hsv[y_start:y_end, x_start:x_end, 1])
            hue = np.mean(hsv[y_start:y_end, x_start:x_end, 0])

            # 检测区域颜色特征
            sky_score = brightness * (1 - sat / 255) * 2  # 天空通常亮且低饱和度
            grass_score = (hue >= 35 and hue <= 85) * sat / 255 * brightness / 255 * 100  # 绿草

            regions.append({
                'pos': (i, j),
                'brightness': brightness,
                'saturation': sat,
                'hue': hue,
                'sky_score': sky_score,
                'grass_score': grass_score
            })

    # 3. 分析图像方向
    # 按天空分数排序
    sky_regions = sorted(regions, key=lambda r: r['sky_score'], reverse=True)
    grass_regions = sorted(regions, key=lambda r: r['grass_score'], reverse=True)

    # 如果有明显的天空区域和草地区域
    if len(sky_regions) > 0 and len(grass_regions) > 0:
        top_sky = sky_regions[0]['pos'][0]  # 行坐标(0-3)
        top_grass = grass_regions[0]['pos'][0]  # 行坐标(0-3)

        # 正确方向: 天空在上(小行号)，草地在下(大行号)
        if top_sky < top_grass:
            angle = 0  # 已经是正确方向
        elif top_sky > top_grass:
            angle = 180  # 上下颠倒
        else:
            # 检查水平位置
            sky_col = sky_regions[0]['pos'][1]
            grass_col = grass_regions[0]['pos'][1]

            if sky_col < grass_col:
                angle = 90  # 需要顺时针旋转90度
            else:
                angle = 270  # 需要顺时针旋转270度
    else:
        # 用备选方法: 基于亮度梯度
        # 天空通常在上方且更亮
        top_half = np.mean(cv2.cvtColor(masked[:height // 2, :], cv2.COLOR_BGR2GRAY))
        bottom_half = np.mean(cv2.cvtColor(masked[height // 2:, :], cv2.COLOR_BGR2GRAY))

        if top_half > bottom_half + 10:
            angle = 0
        elif bottom_half > top_half + 10:
            angle = 180
        else:
            # 左右对比
            left_half = np.mean(cv2.cvtColor(masked[:, :width // 2], cv2.COLOR_BGR2GRAY))
            right_half = np.mean(cv2.cvtColor(masked[:, width // 2:], cv2.COLOR_BGR2GRAY))

            if left_half > right_half + 10:
                angle = 270  # 需要逆时针旋转90度
            elif right_half > left_half + 10:
                angle = 90  # 需要顺时针旋转90度
            else:
                angle = 0  # 无法确定，假设已正确

    if debug:
        print(f"分析结果: 需要旋转 {angle} 度")

        # 在调试图像上标记分析结果
        cv2.putText(debug_img, f"Angle: {angle}", (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

        if len(sky_regions) > 0:
            i, j = sky_regions[0]['pos']
            y = i * quarter_height + quarter_height // 2
            x = j * quarter_width + quarter_width // 2
            cv2.circle(debug_img, (x, y), 20, (255, 0, 0), -1)  # 蓝色标记天空

        if len(grass_regions) > 0:
            i, j = grass_regions[0]['pos']
            y = i * quarter_height + quarter_height // 2
            x = j * quarter_width + quarter_width // 2
            cv2.circle(debug_img, (x, y), 20, (0, 255, 0), -1)  # 绿色标记草地

        cv2.imwrite("rotation_analysis.png", debug_img)

    # 4. 转换角度为滑动距离
    slider_width = 400  # 滑块条宽度，根据实际UI调整

    # 假设滑块从左到右对应 0-360度
    # 注意: 根据实际验证码的行为可能需要调整这个计算
    slide_distance = (angle / 360) * slider_width

    return int(slide_distance)

async def slide_captcha(page, slider, distance):
    """
    模拟人工滑动验证码
    """
    # 获取滑块位置
    slider_box = await slider.bounding_box()
    start_x = slider_box['x'] + slider_box['width'] / 2
    start_y = slider_box['y'] + slider_box['height'] / 2

    # 移动到滑块位置
    await page.mouse.move(start_x, start_y)
    await page.mouse.down()

    # 人性化移动轨迹
    current = 0
    while current < distance:
        # 生成随机速度
        move = min(random.randint(5, 15), distance - current)
        current += move

        # 添加微小的Y轴抖动
        y_offset = random.randint(-3, 3)

        # 移动鼠标
        await page.mouse.move(start_x + current, start_y + y_offset)

        # 随机延迟
        await page.wait_for_timeout(random.randint(5, 15))

    # 在终点附近小幅度调整
    for _ in range(2):
        await page.mouse.move(
            start_x + distance + random.randint(-2, 2),
            start_y + random.randint(-1, 1)
        )
        await page.wait_for_timeout(random.randint(50, 100))

    # 释放鼠标
    await page.wait_for_timeout(random.randint(200, 400))
    await page.mouse.up()

if __name__ == '__main__':
    asyncio.run(run())  # 使用 asyncio.run 来运行协程
